package com.fjwt.gz.proxy;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson2.JSONObject;
import com.fjwt.gz.core.cache.ITokenService;
import com.fjwt.gz.core.cache.RedisUtil;
import com.fjwt.gz.core.constants.CS;
import com.fjwt.gz.core.entity.SysUser;
import com.fjwt.gz.core.exception.BizException;
import com.fjwt.gz.core.exception.GzAuthenticationException;
import com.fjwt.gz.core.jwt.JWTPayload;
import com.fjwt.gz.core.jwt.JWTUtils;
import com.fjwt.gz.core.model.ApiRes;
import com.fjwt.gz.core.model.security.GzUserDetails;
import com.fjwt.gz.core.utils.JsonKit;
import com.fjwt.gz.proxy.config.SystemYmlConfig;
import com.fjwt.gz.proxy.secruity.AgentAwareAuthenticationToken;
import com.fjwt.gz.service.impl.AuthManage;
import com.fjwt.gz.service.impl.SysRoleEntRelaService;
import com.fjwt.gz.service.impl.SysRoleService;
import com.fjwt.gz.service.impl.SysUserService;
import com.fjwt.gz.service.mapper.SysEntitlementMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/*
* 认证Service
*
* @author wg
* @site https://pt.jdyd.xyz/
* @date 2021/6/8 17:12
*/
@Slf4j
@Service
public class AuthService {

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired private SysUserService sysUserService;
    @Autowired private SysRoleService sysRoleService;
    @Autowired private SysRoleEntRelaService sysRoleEntRelaService;
    @Autowired private SysEntitlementMapper sysEntitlementMapper;
    @Autowired private SystemYmlConfig systemYmlConfig;
    @Autowired private AuthManage authManage;

    /** 登录认证(密码登录， 包含MFA二次认证校验) **/
    public ApiRes authByAuthentication(String agentNo, String username, String password, String vercodeToken, String mfaCode){ //String agentNo,

        // 防穷举判断
        authManage.loginErrValidate(CS.SYS_ROLE_TYPE.PROXY, username);

        //1. 生成spring-security usernamePassword类型对象
//        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
        AgentAwareAuthenticationToken upToken = new AgentAwareAuthenticationToken(agentNo, username, password);

        //spring-security 自动认证过程；
        // 1. 进入 JeeUserDetailsServiceImpl.loadUserByUsername 获取用户基本信息；
        //2. SS根据UserDetails接口验证是否用户可用；
        //3. 最后返回loadUserByUsername 封装的对象信息；
        Authentication authentication = null;
        try {
            authentication = authenticationManager.authenticate(upToken);
        } catch (GzAuthenticationException jex) {
            throw jex.getBizException() == null ? new BizException(jex.getMessage()) : jex.getBizException();
        } catch (BadCredentialsException e) {

            // 防穷举累加
            authManage.loginErrCount(CS.SYS_ROLE_TYPE.PROXY, username);

            // throw new BizException("用户名/密码错误！");
        } catch (AuthenticationException e) {
            log.error("AuthenticationException:", e);
            throw new BizException("认证服务出现异常， 请重试或联系系统管理员！");
        }

        GzUserDetails gzUserDetails = (GzUserDetails) authentication.getPrincipal();

        // 默认密码认证方式
        gzUserDetails.setAuthType(JWTPayload.CREDENTIAL_AUTH_TYPE.PASSWORD);

        //验证通过后 再查询用户角色和权限信息集合

        SysUser sysUser = gzUserDetails.getSysUser();

        // 所属机构Id
        gzUserDetails.setAgentNo(sysUser.getBelongInfoId());



        // 普通操作员 && 不包含左侧菜单 进行错误提示 （超管 or 扩展员都是通过菜单定义进行查询全部菜单进行过滤，只有普通操作员通过角色功能进行划分）
        if(sysUser.getUserType() == com.fjwt.gz.db.entity.SysUser.UEST_TYPE_NORMAL
                && sysEntitlementMapper.userHasLeftMenu(sysUser.getSysUserId(), CS.SYS_ROLE_TYPE.PROXY) <= 0){
            throw new BizException("当前用户未分配任何菜单权限，请联系管理员进行分配后再登录！");
        }

        // 放置权限集合
        gzUserDetails.setAuthorities(getUserAuthority(sysUser));

        // 设置唯一登录逻辑
        String oldUUID = RedisUtil.getString("USER:"+sysUser.getSysUserId());
        if(StringUtils.isNotEmpty(oldUUID)){
            String redis = CS.getCacheKeyToken(CS.SYS_ROLE_TYPE.PROXY, sysUser.getSysUserId(), oldUUID);
            RedisUtil.del(redis);
            log.info("redis缓存数据信息：{}",RedisUtil.getString(redis));
        }
        String uuId = IdUtil.fastUUID();
        //生成token
        String cacheKey = CS.getCacheKeyToken(CS.SYS_ROLE_TYPE.PROXY, sysUser.getSysUserId(),uuId);
        RedisUtil.setString("USER:"+sysUser.getSysUserId(),uuId);

        //生成iToken 并放置到缓存
        ITokenService.processTokenCache(CS.SYS_ROLE_TYPE.PROXY, gzUserDetails, cacheKey); //处理token 缓存信息

        //将信息放置到Spring-security context中
        UsernamePasswordAuthenticationToken authenticationRest = new UsernamePasswordAuthenticationToken(gzUserDetails, null, gzUserDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationRest);

        // JWTToken
        String accessToken = JWTUtils.generateToken(new JWTPayload(gzUserDetails), systemYmlConfig.getJwtSecret());

        // 删除图形验证码缓存数据
        // RedisUtil.del(CS.getCacheKeyImgCode(vercodeToken));

        // 更新上次登录时间
        sysUserService.updateLastLoginTime(sysUser.getSysUserId());

        JSONObject tokenRes = JsonKit.newJson(CS.ACCESS_TOKEN_NAME, accessToken);
        tokenRes.put("lastLoginTime", sysUser.getLastLoginTime());
        return ApiRes.ok(tokenRes);
    }

    /** 根据用户ID 更新缓存中的权限集合， 使得分配实时生效  **/
    public void refAuthentication(List<Long> sysUserIdList){

        if(sysUserIdList == null || sysUserIdList.isEmpty()){
            return ;
        }

        Map<Long, SysUser> sysUserMap = new HashMap<>();

        // 查询 sysUserId 和 state
        sysUserService.list(
                com.fjwt.gz.db.entity.SysUser.gw()
                        .select(SysUser::getSysUserId, SysUser::getState)
                        .in(SysUser::getSysUserId, sysUserIdList)
        ).stream().forEach(item -> sysUserMap.put(item.getSysUserId(), item));

        for (Long sysUserId : sysUserIdList) {

            Collection<String> cacheKeyList = RedisUtil.keys(CS.getCacheKeyToken(CS.SYS_ROLE_TYPE.PROXY, sysUserId, "*"));
            if(cacheKeyList == null || cacheKeyList.isEmpty()){
                continue;
            }

            for (String cacheKey : cacheKeyList) {

                //用户不存在 || 已禁用 需要删除Redis
                if(sysUserMap.get(sysUserId) == null || sysUserMap.get(sysUserId).getState() == CS.PUB_DISABLE){
                    RedisUtil.del(cacheKey);
                    continue;
                }

                GzUserDetails jwtBaseUser = RedisUtil.getObject(cacheKey, GzUserDetails.class);
                if(jwtBaseUser == null){
                    continue;
                }

                // 重新放置sysUser对象
                jwtBaseUser.setSysUser(sysUserService.getById(sysUserId));

                //查询放置权限数据
                jwtBaseUser.setAuthorities(getUserAuthority(jwtBaseUser.getSysUser()));

                //保存token  失效时间不变
                RedisUtil.set(cacheKey, jwtBaseUser);
            }
        }

    }

    /** 根据用户ID 删除用户缓存信息  **/
    public void delAuthentication(List<Long> sysUserIdList){
        if(sysUserIdList == null || sysUserIdList.isEmpty()){
            return ;
        }
        for (Long sysUserId : sysUserIdList) {
            Collection<String> cacheKeyList = RedisUtil.keys(CS.getCacheKeyToken(CS.SYS_ROLE_TYPE.PROXY, sysUserId, "*"));
            if(cacheKeyList == null || cacheKeyList.isEmpty()){
                continue;
            }
            for (String cacheKey : cacheKeyList) {
                RedisUtil.del(cacheKey);
            }
        }
    }

    public List<SimpleGrantedAuthority> getUserAuthority(SysUser sysUser){

        //用户拥有的角色集合  需要以ROLE_ 开头,  用户拥有的权限集合
        List<String> roleList = sysRoleService.findListByUser(sysUser.getSysUserId());
        List<String> entList = sysRoleEntRelaService.selectEntIdsByUserId(sysUser.getSysUserId(), sysUser.getUserType(), sysUser.getSysType());

        List<SimpleGrantedAuthority> grantedAuthorities = new LinkedList<>();
        roleList.stream().forEach(role -> grantedAuthorities.add(new SimpleGrantedAuthority(role)));
        entList.stream().forEach(ent -> grantedAuthorities.add(new SimpleGrantedAuthority(ent)));
        return grantedAuthorities;
    }

}
